package edu.cmu.cs211.exprtree.ast;

import java.math.BigDecimal;
import java.util.Map;

/**
 * An expression representing division
 */
public final class DivideExpression extends BinaryExpression {
	
	// fields
	Expression l;
	Expression r;
	
	// constructor
	public DivideExpression(Expression l, Expression r) {
		super(l, r);
		this.l = l;
		this.r = r;
	}

	/*  DivideExpression.evaluate: replaces the variable with the value in hashmap
	 *  and evaluate the expression
	 *  returns a BigDecimal value of the expression
	 */
	public BigDecimal evaluate(Map<String, BigDecimal> variableAssignments) 
	{
		// recurse until the leaf node then start evaluating
		if(l != null || r != null)
		{
			l = new ConstantExpression(l.evaluate(variableAssignments));
			r = new ConstantExpression(r.evaluate(variableAssignments));
		}
		
		String key = null;
		String key2 = null;
		
		//throws an NullPointerException if map is required and is empty
		if(variableAssignments == null && (l.getClass().equals(VariableExpression.class) || r.getClass().equals(VariableExpression.class)))
			throw new NullPointerException();
	
		
		//l / r
		if(l.getClass().equals(VariableExpression.class) && r.getClass().equals(VariableExpression.class))
		{
			if(variableAssignments.containsKey(key = ((VariableExpression)l).getName()) && variableAssignments.containsKey(key2 = ((VariableExpression)r).getName()))
			{
				if (BigDecimalUtils.isZero(variableAssignments.get(key2)))
					throw new ArithmeticException();
					
				return BigDecimalUtils.safeDivide(variableAssignments.get(key), variableAssignments.get(key2));
			}
			else
				throw new IllegalArgumentException();
		}
		//l / c2
		if(l.getClass().equals(VariableExpression.class) && r.getClass().equals(ConstantExpression.class))
		{
			if(variableAssignments.containsKey(key = ((VariableExpression)l).getName()))
			{
				if (BigDecimalUtils.isZero((((ConstantExpression)r).getValue())))
					throw new ArithmeticException();
				return BigDecimalUtils.safeDivide(variableAssignments.get(key), (((ConstantExpression)r).getValue()));
			}
			else
				throw new IllegalArgumentException();
		}
		//c1 / r
		if(r.getClass().equals(VariableExpression.class) && l.getClass().equals(ConstantExpression.class))
		{
			if(variableAssignments.containsKey(key = ((VariableExpression)r).getName()))
			{
				if (BigDecimalUtils.isZero(variableAssignments.get(key)))
					throw new ArithmeticException();
				return BigDecimalUtils.safeDivide(variableAssignments.get(key),(((ConstantExpression)l).getValue()));
			}
			else
				throw new IllegalArgumentException();
		}
		//c1 / c2
		if (BigDecimalUtils.isZero((((ConstantExpression)r).getValue())))
			throw new ArithmeticException();
		return BigDecimalUtils.safeDivide(((ConstantExpression)l).getValue(),(((ConstantExpression)r).getValue()));
	}

	/** @return The division symbol, `/' */
	@Override
	public char getOperatorSymbol() {
		return '/';
	}

	/**
	 * Constant folding for this node. Uses the following rules:
	 * <p>
	 * (cN = constant expr, eN = any expression)
	 * <p>
	 * <ul>
	 * <li>(c1/c2) ??? constant with value c1/c2
	 * <li>(0/e1) ??? 0 (nb: this rule risks turning an NaN into a 0, but you
	 * don't need to worry about that).
	 * <li>(e1/1) ??? e1
	 * <li>(e1/-1) ??? -e1
	 * </ul>
	 */
	public Expression cfold() {
		// recurse until the leaf node and start folding
		if(l != null || r != null)
		{
			l = l.cfold();
			r = r.cfold();
		}
		// c1/c2 
		if(l.getClass().equals(ConstantExpression.class) && r.getClass().equals(ConstantExpression.class))
		{
			return new ConstantExpression( BigDecimalUtils.safeDivide((((ConstantExpression)l).getValue()),(((ConstantExpression)r).getValue())));	
		}
		// 0/c2
		else if (l.getClass().equals(ConstantExpression.class) &&(BigDecimalUtils.isZero(((ConstantExpression)l).getValue())))
		{
			return new ConstantExpression(0);	
		}
		// c1/1 
		else if (r.getClass().equals(ConstantExpression.class) && (BigDecimalUtils.isOne(((ConstantExpression)r).getValue())))
		{
			return l;	
		}
		// c1/(-1) 
		else if (r.getClass().equals(ConstantExpression.class) &&(BigDecimalUtils.isMinusOne(((ConstantExpression)r).getValue())))
		{
			return new NegationExpression(l);	
		}
		// l / r
		return new DivideExpression(l, r);
	}
}
